﻿
using System.Collections.Generic;
using System.Linq;


namespace FacebookXmpp.Parse
{
	internal class DocumentBuilder
	{
		private readonly List<MessageData> _openMessages = new List<MessageData>();
		private readonly List<MessageData> _closedMessages = new List<MessageData>();

		public DocumentBuilder()
		{
			Initialize();
		}

		public void Initialize()
		{
			if (!_openMessages.Any())
				StartElement();
		}

		private MessageData _current;

		public void StartElement()
		{
			_current = new MessageData();
			lock (_openMessages)
				_openMessages.Add( _current );

			UpdateCurrent();
		}

		public MessageData Current
		{
			get { return _current; }
		}

		private void UpdateCurrent()
		{
			lock (_openMessages)
			{
				_current = _openMessages.LastOrDefault();
				if (_current == null)
				{
					_current = new MessageData();
					_openMessages.Add( _current );
				}
			}
		}
		
		/// <summary>
		/// XmlStream has read an Open Element, which has not been closed yet. eg: < something > ....
		/// </summary>
		public bool HasOpenElement
		{
			get { return _openMessages.Any( t => !t.IsEmpty ); }
		}

		/// <summary>
		/// Consume the Open Element (close elements are ignored)
		/// </summary>
		/// <returns></returns>
		public MessageData ConsumeOpenElement()
		{
			MessageData first;
			lock (_openMessages)
			{
				first = _openMessages.FirstOrDefault();
				if (first != null)
					_openMessages.Remove( first );
			}

			UpdateCurrent();
			first.IsOpen = true;
			return first;
		}

		public void PushBack( MessageData messageData )
		{
			lock (_closedMessages)
				_closedMessages.Add( messageData );
		}

        private MessageData ConsumeLastOpenElement()
        {
            MessageData consumed;

			lock (_openMessages)
			{
				consumed = _openMessages.LastOrDefault();

				if (consumed != null)
					_openMessages.Remove( consumed );
			}

        	UpdateCurrent();
            return consumed;
        }

		/// <summary>
		/// XmlStream has read an Open and Close Element. eg: < somthing > .... < /something >
		/// </summary>
		public bool HasClosedElement
		{
			get { return _closedMessages.Any(); }
		}

		/// <summary>
		/// Consume the next MessageData from the stream
		/// </summary>
		/// <returns></returns>
		public MessageData ConsumeClosedElement()
		{
			MessageData consumed;
			lock (_closedMessages)
			{
				consumed = _closedMessages.FirstOrDefault();
				if (consumed == null)
					return null;

				_closedMessages.Remove( consumed );
			}

			// if "empty" XmlData with Value=XmlData, then return inner xmldata
			if ((consumed.Name == null) && (consumed.Attributes.Count() == 0) && (typeof( MessageData ).IsAssignableFrom( consumed.Value.GetType() )))
				return (MessageData)consumed.Value;

			consumed.IsOpen = false;
			return consumed;
		}
		
		public void CloseElement( )
		{
			MessageData read = ConsumeLastOpenElement();

			if (read == null || read.IsEmpty)
				return ;

			// no more open elements, so this must be a complete document ( #opens = #closes )
			if (!HasOpenElement)
			{
				lock (_closedMessages)
					_closedMessages.Add( read );

				Initialize();
				return;
			}

			if ((Current.Value != null) && (typeof( MessageData ).IsAssignableFrom( Current.Value.GetType() )))
			{
				MessageData ba = (MessageData)Current.Value;
				Current.Value = new List<MessageData>();
        
                ((List<MessageData>)Current.Value).Add( ba );
			}

			if ((Current.Value != null) && (typeof( List<MessageData> ).IsAssignableFrom( Current.Value.GetType() )))
			{
				((List<MessageData>)Current.Value).Add( read );
			}
			else
			{
				Current.Value = read;
			}
        }
	}
}